########################################################################
#  Pyfm - personal finance data-base
#  Copyright (C) 2007,8 Ehud Ben-Reuven
#  udi@benreuven.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
########################################################################
"""Auxilary tools for handling objects"""
from flat import break_tag,stagvalue,print_line

def get_filter_tags(filter):
    """Return a list of 3-tuples for every <tag> in filter that has the form <op><raw-tag> that appears in the filter (excpet for -<tag>)
    Each tuple is made from (<tag>,<raw-tag>,<op>)"""
    return [break_tag(tag) for tag in filter if tag[0]!='-'] # Ignore tags that begins with '-'
                
def get_command_tags(filter,filter_tags=None):
    """Build a dictionary of all the query commands that appears in the filter.
    The key is the operation and the value is a list of all the raw-tags that came with that operation:
    {..., <op>: [<raw-tag>,...],...}"""
    if filter_tags==None:
        filter_tags=get_filter_tags(filter)
    command_tags={}
    for tag,raw_tag,op in filter_tags:
        if op!='!': continue
        for v in filter[tag]:
            command_tags.setdefault(v,[]).append(raw_tag)
    return command_tags

def post_filter(res,filter,filter_tags=None):
    # Post process using the filter commands (i.e., <tag>!<command>)
    # Build a dictonary of all commands->tags
    command_tags=get_command_tags(filter,filter_tags)
    for s in reversed(command_tags.get('SORT',[])): # we want the first sorting tag to be the most significant so apply it last
        res.sort(key=lambda r,tag=s: min(r[tag]) if r[tag] else '') # If a tag has multiple values then take its smallest
    maxtag=command_tags.get('MAX',[])
    mintag=command_tags.get('MIN',[])
    assert(len(maxtag+mintag) <= 1)
    if maxtag or mintag:
        for s in maxtag:
            r=max(res,key=lambda r,tag=s: max(r[tag]) if r[tag] else '')
        for s in mintag:
            r=min(res,key=lambda r,tag=s: min(r[tag]) if r[tag] else '')
        res[0]=r
        del res[1:]

def print_lines(filter):
    """This is the pl shell command"""
    from obj import db
    for l in db.getall('Line',filter):
        for t in l:
            if t=='Line': continue
            if '-V' not in filter and t.startswith('_'): continue
            if '-NO' in filter and t in db.name2class: continue
            for v in l[t]:
                print stagvalue(t,v),
        print

def save_lines(line):
    """This is the sl shell command"""
    from obj import db

    fname=line.pop('FILE')[0]
    filter=line.pop('Line')[0]

    print 'Saving to',fname
    fout = open(fname,'w')
    verbose = '-V' in line
        
    for l in db.getall('Line',filter):
        for t in l:
            if t=='Line': continue
            if t in db.name2class: continue
            if not verbose and t.startswith('_'): continue
            for v in l[t]:
                print >>fout,stagvalue(t,v),
        print >>fout
    fout.close()

def print_lines_content(filter):
    """This is the p shell command
    Perform a query on DB using the filter and print the results.
    if filter has the key '-PPRINT'
    then the results are printed in pretty way."""
    from obj import db,Obj
    filter_tags=get_filter_tags(filter)
    command_tags=get_command_tags(filter,filter_tags)
    if '-PLINE' in filter:  # -PLINE - print the filter itself.
        res=[filter]
    else:
        # Check if there are tags that should be printed
        if 'GET' not in command_tags and 'PRINT' not in command_tags:
            # if there is no <tag>!GET or <tag>!PRINT and
            # if there is just one tag in the filter then assume we want to GET it.
            for filter_tag in filter_tags:
                if filter_tag[2]!='=': continue
                tag=filter_tag[1]
                filter['!'+tag]=['GET']
        res=db.getalllines(filter) # Perform the query.
    if not res: return
    
    # Check if there are no fancy print options
    if '-PPRINT' not in filter and '-PFULL' not in filter:
        for r in res:
            for t in r:
                for v in r.get(t,[]):
                    print stagvalue(t,v),
            print
        print_sum(command_tags,res)
        return
        
    expand=len(res)==1
    # Check if all results are made from the same single tag
    t=res[0].keys()[0]
    if all(len(r)==1 and t in r for r in res):
        # If so then print the tag just once
        print t+'='
        first_value=res[0][t][0]
        if isinstance(first_value,Obj):
            # Print the entire object content and not its key
            for r in res:
                r[t][0].pprint(expand=expand,full='-PFULL' in filter)
                if r!=res[-1]:
                    print '===================='
        else:
            for r in res:
                for v in r[t]:
                    print stagvalue('',v),
                print
        return

    for r in res:
        print_line(r,expand=expand)
        if not expand: print

def print_objs(filter):
    """This is the po shell command"""
    from obj import db,Obj
    # Check if there are tags that should be printed
    filter_tags=get_filter_tags(filter)
    command_tags=get_command_tags(filter,filter_tags)
    tag=[]
    for filter_tag in filter_tags:
        if filter_tag[2]!='=': continue
        tag.append(filter_tag[1])
    if len(tag)!=1: raise Exception('Only one object')
    tag=tag[0]
    # The filter[tag] could be happy for example if the user enters something
    # like <tag>=|<tag1>=<value>| but tag1 does not appear in the class tag
    # and not in any of the other classes directly or indirectly used by tag
    if not filter[tag]:
        return
    res=db.getallobjects(tag,filter[tag][0]) # Perform the query.
    if not res: return
    
    if any(op in command_tags for op in ['PRINT' ,'SUM']):
        if 'PRINT' in command_tags:
            for r in res:
                for tag in command_tags['PRINT']:
                    print stagvalue(tag,r[tag]),
                print
        print_sum(command_tags,res)
    elif any(c in filter for c in ['-PPRINT','-PFULL']):
        for r in res:
            r.pprint(expand=True,full='-PFULL' in filter)
            print '===================='
    else:
        for r in res:
            print r

def print_sum(command_tags={},res=[],filter=None):
    if filter:
        assert not command_tags
        command_tags=get_command_tags(filter)
    if 'SUM' not in command_tags: return
    tagsum={}
    for tag in command_tags['SUM']: tagsum[tag]=0
    for r in res:
        for tag in command_tags['SUM']:
            tagsum[tag]+=sum(r[tag])
    print 'SUM',
    for tag in command_tags['SUM']:
        print stagvalue(tag,tagsum[tag]),
    print           
